// select_server.hpp封装select服务器
#pragma once
#include "Socket.hpp"
#include <sys/select.h>

#define NUM 1024
#define DFL_FD -1
#define BACK_LOG 5
class SelectServer
{
public:
    SelectServer(int port)
        : _port(port)
    {
    }
    void InitSelectServer() // 初始化
    {
        _LisSocFd = Socket::SocCreate();
        Socket::SocketBind(_LisSocFd, _port);
        Socket::SocketListen(_LisSocFd, BACK_LOG);
    }
    ~SelectServer()
    {
        if (_LisSocFd >= 0)
        {
            close(_LisSocFd);
        }
    }
    void HandlerEvent(const fd_set &readfds, int fd_array[], int num) // 事件就绪之后的处理
    {
        for (int i = 0; i < num; i++)//遍历保存着需要被select监视的文件描述符的数组
        {
            if (fd_array[i] == DFL_FD)//跳过无效的文件描述符
                continue;
            if (fd_array[i] == _LisSocFd && FD_ISSET(_LisSocFd, &readfds))//对处于就绪状态的监听套接字的处理
            {
                struct sockaddr_in peer;
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sockFd = accept(_LisSocFd, (struct sockaddr*)&peer, &len);//获取连接
                if (sockFd < 0)
                {
                    std::cerr << "accept error" << std::endl;
                    continue;
                }
                std::string peer_ip = inet_ntoa(peer.sin_addr);
                int peer_port = ntohs(peer.sin_port);
                std::cout << "Get a link sucessfully!" << peer_ip << ":" << peer_port << std::endl;
                 //将获取到的套接字添加到fds数组中，并关心其读事件
                //如果将该文件描述符无法添加进数组直接关闭该文件描述符
                if (!SetFdArray(fd_array, num, sockFd))
                {
                    close(sockFd);
                    std::cout << "select server is full,close fd:" << sockFd << std::endl;
                }
            }
            else if (FD_ISSET(fd_array[i], &readfds))//读事件就绪
            {
                char buffer[1024];
                ssize_t s = read(fd_array[i], buffer, sizeof(buffer) - 1);
                if (s > 0)//读取成功
                {
                    buffer[s] = '\0';
                    std::cout << "echo#:" << buffer << std::endl;
                }
                else if (s == 0)//读事件就绪，但是在读的时候没有读到，只能是对端的连接关闭
                {
                    std::cout << "Client quit!" << std::endl;
                    close(fd_array[i]);
                    fd_array[i] = DFL_FD;//将该文件描述符从数组中删除
                }
                else
                {
                    std::cerr << "read error" << std::endl;
                    close(fd_array[i]);
                    fd_array[i] = DFL_FD;
                }
            }
        }
    }
    void Run()
    {
        fd_set readfds;                  // 读文件描述符集
        int fd_array[NUM];               // 设置数组，其中保存需要被select监视读事件的文件描述符
        SetClear(fd_array, NUM, DFL_FD); // 初始化，将数组中的所有位置都设置为无效
        fd_array[0] = _LisSocFd;         // 将监听套接字放进数组中，即让select监视该文件描述符的读事件
        for (;;)
        {
            FD_ZERO(&readfds);            // 清空读文件描述符集
            int maxfd = DFL_FD;           // 记录最大的文件描述符
            for (int i = 0; i < NUM; i++) // 遍历数组
            {
                if (fd_array[i] == DFL_FD) // 跳过无效的数组
                    continue;
                FD_SET(fd_array[i], &readfds); // 将有效位置的文件描述符添加到readfds当中
                if (fd_array[i] > maxfd)       // 更新最大的文件描述符
                    maxfd = fd_array[i];
            }
            // struct timeval timeout ={5,0};
            switch (select(maxfd + 1, &readfds, nullptr, nullptr, nullptr))
            {
            case 0:
                std::cout << "timeout..." << std::endl;
                break;
            case -1:
                std::cerr << "select error" << std::endl;
                break;
            default:
                // 这里正常处理事件
                //  std::cout << "Has Done!" << timeout.tv_sec<<std::endl;
                // handle events!
                HandlerEvent(readfds, fd_array, NUM);
                break;
            }
        }
    }

private:
    void SetClear(int fd_array[], int num, int default_fd)
    {
        for (int i = 0; i < num; i++)
        {
            fd_array[i] = default_fd;
        }
    }
    bool SetFdArray(int fd_array[], int num, int fd)//给数组中添加新的需要被监视的文件描述符
    {
        for (int i = 0; i < num; i++)
        {
            if (fd_array[i] == DFL_FD)
            {
                fd_array[i] = fd;
                return true;
            }
        }
        return false;
    }

private:
    int _port;
    int _LisSocFd;
};
